import typing
import typing_extensions
import baml_py

from . import stream_types, types, type_builder
from .parser import LlmResponseParser, LlmStreamParser
from .runtime import DoNotUseDirectlyCallManager, BamlCallOptions
from .globals import DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME as __runtime__

class BamlSyncClient:
    __options: DoNotUseDirectlyCallManager
    __stream_client: "BamlStreamClient"
    __http_request: "BamlHttpRequestClient"
    __http_stream_request: "BamlHttpStreamRequestClient"
    __llm_response_parser: LlmResponseParser
    __llm_stream_parser: LlmStreamParser

    def __init__(self, options: DoNotUseDirectlyCallManager):
        self.__options = options
        self.__stream_client = BamlStreamClient(options)
        self.__http_request = BamlHttpRequestClient(options)
        self.__http_stream_request = BamlHttpStreamRequestClient(options)
        self.__llm_response_parser = LlmResponseParser(options)
        self.__llm_stream_parser = LlmStreamParser(options)

    def __getstate__(self):
        # Return state needed for pickling
        return {"options": self.__options}

    def __setstate__(self, state):
        # Restore state from pickling
        self.__options = state["options"]
        self.__stream_client = BamlStreamClient(self.__options)
        self.__http_request = BamlHttpRequestClient(self.__options)
        self.__http_stream_request = BamlHttpStreamRequestClient(self.__options)
        self.__llm_response_parser = LlmResponseParser(self.__options)
        self.__llm_stream_parser = LlmStreamParser(self.__options)

    def with_options(self,
        tb: typing.Optional[type_builder.TypeBuilder] = None,
        client_registry: typing.Optional[baml_py.baml_py.ClientRegistry] = None,
        collector: typing.Optional[typing.Union[baml_py.baml_py.Collector, typing.List[baml_py.baml_py.Collector]]] = None,
        env: typing.Optional[typing.Dict[str, typing.Optional[str]]] = None,
        tags: typing.Optional[typing.Dict[str, str]] = None,
        on_tick: typing.Optional[typing.Callable[[str, baml_py.baml_py.FunctionLog], None]] = None,
    ) -> "BamlSyncClient":
        options: BamlCallOptions = {}
        if tb is not None:
            options["tb"] = tb
        if client_registry is not None:
            options["client_registry"] = client_registry
        if collector is not None:
            options["collector"] = collector
        if env is not None:
            options["env"] = env
        if tags is not None:
            options["tags"] = tags
        if on_tick is not None:
            options["on_tick"] = on_tick
        return BamlSyncClient(self.__options.merge_options(options))

    @property
    def stream(self):
      return self.__stream_client

    @property
    def request(self):
      return self.__http_request

    @property
    def stream_request(self):
      return self.__http_stream_request

    @property
    def parse(self):
      return self.__llm_response_parser

    @property
    def parse_stream(self):
      return self.__llm_stream_parser
    
    {% for func in functions -%}
    def {{ func.name }}(self, {% for arg in func.args -%}
        {{arg.name}}: {{arg.type_.serialize_type(pkg)}}{% if let Some(default_value) = arg.default_value %} = {{default_value}}{% endif %},
        {%- endfor %}
        baml_options: BamlCallOptions = {},
    ) -> {{func.return_type.serialize_type(pkg)}}:
        {% if let Some(docstring) = func.documentation -%}
        {{crate::utils::prefix_lines(docstring, "# ")}}
        {% endif -%}

        # Check if on_tick is provided
        if 'on_tick' in baml_options:
            stream = self.stream.{{ func.name }}({% for arg in func.args -%}
                {{arg.name}}={{arg.name}},
                {%- endfor %}
                baml_options=baml_options)
            return stream.get_final_response()
        else:
            # Original non-streaming code
            result = self.__options.merge_options(baml_options).call_function_sync(function_name="{{ func.name }}", args={
                {% for arg in func.args -%}
                "{{arg.name}}": {{arg.name}},
                {%- endfor %}
            })
            return typing.cast({{func.return_type.serialize_type(pkg)}}, result.cast_to(types, types, stream_types, False, __runtime__))
    {% endfor %}


class BamlStreamClient:
    __options: DoNotUseDirectlyCallManager

    def __init__(self, options: DoNotUseDirectlyCallManager):
        self.__options = options

    {% for func in functions -%}
    def {{ func.name }}(self, {% for arg in func.args -%}
        {{arg.name}}: {{arg.type_.serialize_type(pkg)}}{% if let Some(default_value) = arg.default_value %} = {{default_value}}{% endif %},
        {%- endfor %}
        baml_options: BamlCallOptions = {},
    ) -> baml_py.BamlSyncStream[{{ func.stream_return_type.serialize_type(pkg) }}, {{func.return_type.serialize_type(pkg)}}]:
        {% if let Some(docstring) = func.documentation -%}
        {{crate::utils::prefix_lines(docstring, "# ")}}
        {% endif -%}
        ctx, result = self.__options.merge_options(baml_options).create_sync_stream(function_name="{{ func.name }}", args={
            {% for arg in func.args -%}
            "{{arg.name}}": {{arg.name}},
            {%- endfor %}
        })
        return baml_py.BamlSyncStream[{{ func.stream_return_type.serialize_type(pkg) }}, {{ func.return_type.serialize_type(pkg) }}](
          result,
          lambda x: typing.cast({{func.stream_return_type.serialize_type(pkg)}}, x.cast_to(types, types, stream_types, True, __runtime__)),
          lambda x: typing.cast({{func.return_type.serialize_type(pkg)}}, x.cast_to(types, types, stream_types, False, __runtime__)),
          ctx,
        )
    {% endfor %}

class BamlHttpRequestClient:
    __options: DoNotUseDirectlyCallManager

    def __init__(self, options: DoNotUseDirectlyCallManager):
        self.__options = options

    {% for func in functions -%}
    def {{ func.name }}(self, {% for arg in func.args -%}
        {{arg.name}}: {{arg.type_.serialize_type(pkg)}}{% if let Some(default_value) = arg.default_value %} = {{default_value}}{% endif %},
        {%- endfor %}
        baml_options: BamlCallOptions = {},
    ) -> baml_py.baml_py.HTTPRequest:
        {% if let Some(docstring) = func.documentation -%}
        {{crate::utils::prefix_lines(docstring, "# ")}}
        {% endif -%}

        result = self.__options.merge_options(baml_options).create_http_request_sync(function_name="{{ func.name }}", args={
            {% for arg in func.args -%}
            "{{arg.name}}": {{arg.name}},
            {%- endfor %}
        }, mode="request")
        return result
    {% endfor %}

class BamlHttpStreamRequestClient:
    __options: DoNotUseDirectlyCallManager

    def __init__(self, options: DoNotUseDirectlyCallManager):
        self.__options = options

    {% for func in functions -%}
    def {{ func.name }}(self, {% for arg in func.args -%}
        {{arg.name}}: {{arg.type_.serialize_type(pkg)}}{% if let Some(default_value) = arg.default_value %} = {{default_value}}{% endif %},
        {%- endfor %}
        baml_options: BamlCallOptions = {},
    ) -> baml_py.baml_py.HTTPRequest:
        {% if let Some(docstring) = func.documentation -%}
        {{crate::utils::prefix_lines(docstring, "# ")}}
        {% endif -%}

        result = self.__options.merge_options(baml_options).create_http_request_sync(function_name="{{ func.name }}", args={
            {% for arg in func.args -%}
            "{{arg.name}}": {{arg.name}},
            {%- endfor %}
        }, mode="stream")
        return result
    {% endfor %}

b = BamlSyncClient(DoNotUseDirectlyCallManager({}))
